import java.util.Random;

import tester.*;

import draw.*;
import geometry.*;
import colors.*;


/**
 * Copyright 2008 Viera K. Proulx
 * This program is distributed under the terms of the 
 * GNU Lesser General Public License (LGPL)
 */

/** Class that represents a colored disk that moves around the Canvas */
class Blob{

  Posn center;
  int radius;
  IColor col;

  /** The constructor */
  Blob(Posn center, int radius, IColor col) {
    this.center = center;
    this.radius = radius;
    this.col = col;
  }

  /** draw this blob in the given World */
  boolean draw(Canvas c) {
    return c.drawDisk(this.center, this.radius, col);
  } 

  /** move this blob 20 pixels in the direction given by the ke
      or change its color to Green, Red or Yellow */
  public Blob moveBlob(String ke){
    if (ke.equals("right")){
      return new Blob(new Posn(this.center.x + 5, this.center.y),
                      this.radius, this.col);
    }
    else if (ke.equals("left")){
      return new Blob(new Posn(this.center.x - 5, this.center.y),
      this.radius, this.col);
    }
    else if (ke.equals("up")){
      return new Blob(new Posn(this.center.x, this.center.y - 5),
      this.radius, this.col);
    }
    else if (ke.equals("down")){
      return new Blob(new Posn(this.center.x, this.center.y + 5),
      this.radius, this.col);
    }
    // change the color to Y, G, R
    else if (ke.equals("Y")){
      return new Blob(this.center, this.radius, new Yellow());
    }    
    else if (ke.equals("G")){
      return new Blob(this.center, this.radius, new Green());
    }    
    else if (ke.equals("R")){
      return new Blob(this.center, this.radius, new Red());
    }
    else
      return this;
  }

  /** produce a new blob moved by a random distance < n pixels */
  Blob randomMove(int n){
    return new Blob(new Posn(this.center.x + this.randomInt(n),
                           this.center.y + this.randomInt(n)),
                    this.radius, this.col);
  }

  /** helper method to generate a random number in the range -n to n */
  int randomInt(int n){
    return -n + (new Random().nextInt(2 * n + 1));
  }

  /** is the blob outside the bounds given by the width and height */
  boolean outsideBounds(int width, int height) {
    return this.center.x < 0
    || this.center.x > width
    || this.center.y < 0 
    || this.center.y > height;
  }
  
  /** is the blob near the center of area given by the width and height */
  boolean nearCenter(int width, int height) {
    return this.center.x > width / 2 - 10
    && this.center.x < width / 2 + 10
    && this.center.y > height / 2 - 10
    && this.center.y < height / 2 + 10;
  }
}

/** Represent the world of a Blob */
class BlobWorldFun extends World {

  int width = 200;
  int height = 300;
  Blob blob;

  /** The constructor */
  public BlobWorldFun(Blob blob) {
    super();
    this.blob = blob;
  }

  /** Move the Blob when the player presses a key */
  public World onKeyEvent(String ke) {
    return new BlobWorldFun(blob.moveBlob(ke));
  }

  /** On tick check whether the Blob is out of bounds,
   * or fell into the black hole in the middle.
   * If all is well, move the Blob in a random direction.
   */
  public World onTick() {
    // if the blob is outside the canvas, stop
    if (this.blob.outsideBounds(this.width, this.height)){
      return this.endOfWorld("Blob is outside the bounds");
    }

    // time ends is the blob falls into the black hole in the middle
    if (this.blob.nearCenter(this.width, this.height) &&
      this.endOfTime("Black hole ate the blob"))
      return this;

    // else move the blob randomly at most 5 pixels in any direction
    else
      return new BlobWorldFun(this.blob.randomMove(5));
  }

  /** draw this world with a blue background */
  public boolean draw() {
    return
    this.theCanvas.drawRect(new Posn(0, 0), 
        this.width, this.height, new Blue()) &&
    this.blob.draw(this.theCanvas) &&
    this.theCanvas.drawDisk(new Posn(100, 150), 10, new Black()) &&
    this.theCanvas.drawCircle(new Posn(100, 150), 10, new White()) &&
    this.theCanvas.drawRect(new Posn(95, 145), 10, 10, new White()) &&
    this.theCanvas.drawLine(new Posn(95, 145), new Posn(105, 155), new Red()) &&
    this.theCanvas.drawLine(new Posn(95, 155), new Posn(105, 145), new Red());
  }
}

class Examples{
  // examples of data for the Blob class:
  Blob b1 = new Blob(new Posn(100, 100), 50, new Red());
  Blob b1left = new Blob(new Posn(95, 100), 50, new Red());
  Blob b1right = new Blob(new Posn(105, 100), 50, new Red());
  Blob b1up = new Blob(new Posn(100, 95), 50, new Red());
  Blob b1down = new Blob(new Posn(100, 105), 50, new Red());
  Blob b1G = new Blob(new Posn(100, 100), 50, new Green());
  Blob b1Y = new Blob(new Posn(100, 100), 50, new Yellow());
  
  /** reset the Blob */
  void reset(){
    b1 = new Blob(new Posn(100, 100), 50, new Red());
  }

  /** test the method moveBlob in the Blob class */
  boolean testMoveBlob(Tester t){
  	return
    t.checkExpect(b1.moveBlob("left"), 
                  b1left, "test moveBolb - left " + "\n") &&
    t.checkExpect(b1.moveBlob("right"), 
                  b1right, "test movelob - right " + "\n") &&
    t.checkExpect(b1.moveBlob("up"), 
                  b1up, "test moveBlob - up " + "\n") &&
    t.checkExpect(b1.moveBlob("down"), 
                  b1down, "test moveBlob - down " + "\n") &&
    t.checkExpect(b1.moveBlob("G"), 
                  b1G, "test moveBlob - G " + "\n") &&  
    t.checkExpect(b1.moveBlob("Y"), 
                  b1Y, "test moveBlob - Y " + "\n") && 
    t.checkExpect(b1G.moveBlob("R"), 
                  b1, "test moveBlob - R " + "\n");  
  }

  /** test the method outsideBounds in the Blob class */
  boolean testOutsideBounds(Tester t){
  	return
    t.checkExpect(b1.outsideBounds(60, 200), true,
    "test outsideBounds on the right") &&

    t.checkExpect(b1.outsideBounds(100, 90), true,
    "test outsideBounds below") &&

    t.checkExpect(
        new Blob(new Posn(-5, 100), 50, new Red()).outsideBounds(100, 110), 
        true,
    "test outsideBounds above") &&

    t.checkExpect(
        new Blob(new Posn(80, -5), 50, new Blue()).outsideBounds(100, 90), 
        true,
    "test outsideBounds on the left") &&

    t.checkExpect(b1.outsideBounds(200, 400), false,
    "test outsideBounds - within bounds");
  }

  /** test the method nearCenter in the Blob class */
  boolean testNearCenter(Tester t){
  	return
    t.checkExpect(b1.nearCenter(200, 200), true,
    "test nearCenter - true") &&
    t.checkExpect(b1.nearCenter(200, 100), false,
    "test nearCenter - false");
  }

  /** the method randomInt in the Blob class */
  boolean testRandomInt(Tester t){
  	return
    t.checkOneOf("test randomInt", b1.randomInt(3), -3, -2, -1, 0, 1, 2, 3) &&
    t.checkNoneOf("test randomInt", b1.randomInt(3), -5, -4, 4, 5);
  }

  /** the method randomMove in the Blob class */
  boolean testRandomMove(Tester t){
  	return 
    t.checkOneOf("test randomMove", b1.randomMove(1),
    		               new Blob(new Posn( 99,  99), 50, new Red()),
                       new Blob(new Posn( 99, 100), 50, new Red()),
                       new Blob(new Posn( 99, 101), 50, new Red()),
                       new Blob(new Posn(100,  99), 50, new Red()),
                       new Blob(new Posn(100, 100), 50, new Red()),
                       new Blob(new Posn(100, 101), 50, new Red()),
                       new Blob(new Posn(101,  99), 50, new Red()),
                       new Blob(new Posn(101, 100), 50, new Red()),
                       new Blob(new Posn(101, 101), 50, new Red()));
  }  
  
  /** run the animation */
  boolean testRun(Tester t){
    return t.checkExpect(this.go());
  }
  
  /** run the animation */  
  boolean go(){
    // construct an instance of a TimerWorld
    BlobWorldFun w = 
      new BlobWorldFun(new Blob(new Posn(100, 200), 20, new Red()));
    // and run the TimerWorld
    return w.bigBang(200, 300, 0.3);  	
  }
}

